package mt.comm;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.LinkedList;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import mt.Order;
import mt.comm.ClientSideMessage.Type;

/**
 *@author Rui Rosa
 */

public class CommunicationsServer extends Thread implements ServerComm {

	private ServerSocket socket;
	private int port;
	private int capacity = 50;
	private LinkedList<ClientThread> connectedClientsList;
	private Thread connectingClients;
	private BlockingQueue<ServerSideMessage> queue;

	public CommunicationsServer(int port){
		this.port = port;
		queue = new ArrayBlockingQueue<ServerSideMessage>(capacity);
		connectedClientsList = new LinkedList<ClientThread>();
		this.start();
	}

	/**
	 * Starts the MicroTrader server. The method should setup a server socket and begin to accept connections.
	 * This method must return immediately after a server socket has been setup, and another thread should
	 * started to listen for connections.
	 */

	@Override
	public void start() {
		try {
			socket = new ServerSocket(port);
			connectingClients = new Thread(this);
			connectingClients.start();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Running thread listening to new connections. Accepts each client and stores it in a connectedClientsList which will set 
	 * for each client a listening running Thread that'll be waiting for new messages.
	 */

	@Override
	public void run() {
		while(!connectingClients.isInterrupted()) {	
			try {
				Socket clientSocket = socket.accept();
				ClientThread client = new ClientThread(clientSocket, this);
				connectedClientsList.add(client);
				client.start();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * Adds messages listened by the server(ClientThread in the connectedClientsList) to the blocking queue.
	 * 
	 * @param message
	 */

	public void addToQueue(ServerSide message){
		try {
			queue.put(message);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Get the next message received from one of the clients. If no message 
	 * is has been received, the method blocks until a message 
	 * is received. If no message will ever be received, <b>null</b> is returned.
	 * 
	 * @return The next message sent by one of the connected clients to the server
	 */

	@Override
	public ServerSideMessage getNextMessage() {
		ServerSideMessage message = null;
		try {
			message = queue.take();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		return message;
	}

	/** 
	 * Checks if a message from a client is pending. If {@link #hasNextMessage()} returns <b>true</b>, a call to {@link #getNextMessage} 
	 * will return immediately with the oldest message in the queue. If {@link #hasNextMessage()} returns <b>false</b>, a call to 
	 * {@link #getNextMessage} will block until a message has been received.
	 * 
	 * @return <b>true</b> if a message from a client is currently pending, otherwise <b>false</b>.
	 */

	@Override
	public boolean hasNextMessage() {
		return !queue.isEmpty();
	}

	/**
	 * Send an order to a client.
	 * 
	 * @param receiversNickname nickname of the client to receive the order. If no client with the nickname is currently connected, no order is sent.
	 * @param order order to send. 
	 */

	@Override
	public void sendOrder(String receiversNickname, Order order) {
		ClientSide message = null;
		message = new ClientSide(Type.ORDER, order, null);
		for(int i = 0; i!=connectedClientsList.size(); i++){
			if(connectedClientsList.get(i).getNickname().equals(receiversNickname))
				connectedClientsList.get(i).messageSending(message);
		}
	}

	/**
	 * Report an error to a client.
	 * 
	 * @param toNickname nickname of the client.
	 * @param error description of the error.
	 */

	@Override
	public void sendError(String toNickname, String error) {
		ClientSide message = null;
		message = new ClientSide(Type.ERROR, null, error);
		for(int i = 0; i!=connectedClientsList.size(); i++){
			if(connectedClientsList.get(i).getNickname().equals(toNickname))
				connectedClientsList.get(i).messageSending(message);
		}
	}

	/**
	 * Checks if a client with a certain nickname is currently connected.
	 * 
	 * @param nickname nickname of the client to check.
	 * @return <b>true</b> if a client with the nickname is currently connected, otherwise <b>false</b>.
	 */

	@Override
	public boolean clientIsConnected(String nickname) {
		boolean isConnected = false;
		for(int i = 0; i < connectedClientsList.size(); i++){
			if(connectedClientsList.get(i).getNickname().equals(nickname))
				isConnected = true;
		}
		return isConnected;
	}

	/**
	 * Disconnect a client.
	 * 
	 * @param nickname nickname of the client to disconnect.
	 */

	@Override
	public void disconnectClient(String nickname) {
		if(!connectedClientsList.isEmpty()){
			for(int i = 0; i < connectedClientsList.size(); i++){
				if(connectedClientsList.get(i).getNickname().equals(nickname)){
					connectedClientsList.get(i).disconnect();
					connectedClientsList.remove(i); 

				}
			}
		}
	}

	/**
	 * Disconnects the server and shutdowns all connections
	 */
	
	public void disconnectServer(){
		connectingClients.interrupt();
		while (!connectedClientsList.isEmpty()){
			disconnectClient(connectedClientsList.poll().getNickname());
		}
	}

}